package plugin

import (
	"context"
	"fmt"
	"strings"

	"go.mongodb.org/mongo-driver/bson/primitive"
	mongodb "go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type MongoType struct {
	Type[struct {
		Url      string `env:"MONGO_URL"`
		Database string `env:"MONGO_DATABASE"`
		Username string `env:"MONGO_USERNAME"`
		Password string `env:"MONGO_PASSWORD"`
	}]

	object      *mongodb.Client
	database    *mongodb.Database
	collections map[string]*mongodb.Collection
}

type (
	MongoObjectId = primitive.ObjectID
)

func NewMongoObjectId() MongoObjectId {

	return primitive.NewObjectID()
}

func MongoObjectIdString(value string) (result MongoObjectId) {

	result, _ = primitive.ObjectIDFromHex(value)

	return
}

func (mongo *MongoType) Init() (err error) {

	mongo.collections = map[string]*mongodb.Collection{}

	if mongo.object, err = mongodb.Connect(context.Background(), options.Client().ApplyURI(fmt.Sprintf("mongodb://%s:%s@%s",
		mongo.config.Username,
		mongo.config.Password,
		mongo.config.Url,
	))); err == nil {
		mongo.database = mongo.object.Database(mongo.config.Database)
	}

	return
}

func (mongo *MongoType) Close() {

	mongo.object.Disconnect(context.Background())
}

func (mongo *MongoType) collection(name string) *mongodb.Collection {

	if mongo.collections[name] == nil {
		mongo.collections[name] = mongo.database.Collection(name)
	}

	return mongo.collections[name]
}

func (mongo *MongoType) FindMany(collection string, tables any) (err error) {

	var (
		cursor      *mongodb.Cursor
		findoptions options.FindOptions
	)

	findoptions.SetSort(map[string]any{"_id": 1})

	if cursor, err = mongo.collection(collection).Find(context.Background(), map[string]any{}, &findoptions); err == nil {
	} else if strings.Contains(err.Error(), "no documents in result") {
		err = nil
	} else {
		return
	}

	defer cursor.Close(context.Background())

	err = cursor.All(context.Background(), tables)

	return
}

func (mongo *MongoType) Insert(collection string, table any) (err error) {

	_, err = mongo.collection(collection).InsertOne(
		context.Background(),
		table,
	)

	return
}

func (mongo *MongoType) Delete(collection string, id MongoObjectId) (err error) {

	_, err = mongo.collection(collection).DeleteOne(
		context.Background(),
		map[string]any{"_id": id},
	)

	return
}

func (mongo *MongoType) Update(collection string, id MongoObjectId, data any) (err error) {

	_, err = mongo.collection(collection).UpdateOne(
		context.Background(),
		map[string]any{"_id": id},
		map[string]any{"$set": data},
	)

	return
}

func (mongo *MongoType) Push(collection string, id MongoObjectId, key string, data any) (err error) {

	_, err = mongo.collection(collection).UpdateOne(
		context.Background(),
		map[string]any{"_id": id},
		map[string]any{"$push": map[string]any{key: data}},
	)

	return
}
